# /**
# * Copyright (c) 2022 Beijing Jiaotong University
# * PhotLab is licensed under [Open Source License].
# * You can use this software according to the terms and conditions of the [Open Source License].
# *
# * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
# *
# * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# *
# * See the [Open Source License] for more details.
# */
# /**
# * Author: Shihao Xie
# * Created: Feb. 8, 2023
# * Supported by: National Key Research and Development Program of China
# */
import datetime
import os
import re
import sys
import toml
from phot_server.server_config.minio_config import MINIO_CONF
# from minio import Minio
from decimal import Decimal
from toml.decoder import InlineTableDict
from phot_server.core.exception import FilePathError
from phot_server.utils.minio_api import is_minio_path, split_minio_path
from phot_server.utils.minio_api import Bucket

if sys.version_info >= (3,):
    unicode = str


###### common function
def parse_toml(file_path: str):
    if file_path is None:
        raise FilePathError("The graph or component file path is None.")

    if is_minio_path(file_path):
        client = Bucket(**MINIO_CONF)
        bucket, path = split_minio_path(file_path)
        if client.bucket_exists(bucket) is False:
            raise FilePathError("The bucket '{}' is not exist in minio.".format(file_path))
        file_data = client.get_file_data(bucket, path)
        return toml.loads(file_data)

    if os.path.exists(file_path) is False:
        raise FilePathError("The file path '{}' is not exist.".format(file_path))

    os.chmod(file_path, 0o777)
    with open(file_path, "r", encoding="utf-8") as file:
        data = toml.load(file)
    return data


def save_toml(file_path: str, obj_dict):
    if file_path is None:
        raise FilePathError("The file path is None.")

    if is_minio_path(file_path):
        client = Bucket(**MINIO_CONF)
        bucket, path = split_minio_path(file_path)
        client.save_file_data(bucket, path, toml.dumps(obj_dict))
        return

    file_path = os.path.realpath(file_path)
    file_dir = os.path.dirname(file_path)
    if os.path.exists(file_dir) is False:
        os.makedirs(file_dir)
    try:
        with open(file_path, "w+", encoding="utf-8") as file:
            toml.dump(obj_dict, file, encoder=TomlEncoder())
    except Exception as e:
        print(111)
        with open("./log.txt","w+") as f:
            f.write(str(datetime.datetime.now())+"\n")
            f.write(file_path + " " + str(e)+"\n")
            f.write(str(obj_dict)+"\n")
    os.chmod(file_path, 0o777)

def _dump_str(v):
    if sys.version_info < (3,) and hasattr(v, 'decode') and isinstance(v, str):
        v = v.decode('utf-8')
    v = "%r" % v
    if v[0] == 'u':
        v = v[1:]
    singlequote = v.startswith("'")
    if singlequote or v.startswith('"'):
        v = v[1:-1]
    if singlequote:
        v = v.replace("\\'", "'")
        v = v.replace('"', '\\"')
    v = v.split("\\x")
    while len(v) > 1:
        i = -1
        if not v[0]:
            v = v[1:]
        v[0] = v[0].replace("\\\\", "\\")
        # No, I don't know why != works and == breaks
        joinx = v[0][i] != "\\"
        while v[0][:i] and v[0][i] == "\\":
            joinx = not joinx
            i -= 1
        if joinx:
            joiner = "x"
        else:
            joiner = "u00"
        v = [v[0] + joiner + v[1]] + v[2:]
    return unicode('"' + v[0] + '"')


def _dump_float(v):
    return "{}".format(v).replace("e+0", "e+").replace("e-0", "e-")


def _dump_time(v):
    utcoffset = v.utcoffset()
    if utcoffset is None:
        return v.isoformat()
    # The TOML norm specifies that it's local time thus we drop the offset
    return v.isoformat()[:-6]


class TomlEncoder(object):

    def __init__(self, _dict=dict, preserve=False):
        self._dict = _dict
        self.preserve = preserve
        self.dump_funcs = {
            str: _dump_str,
            unicode: _dump_str,
            list: self.dump_list,
            bool: lambda v: unicode(v).lower(),
            int: lambda v: v,
            float: _dump_float,
            Decimal: _dump_float,
            datetime.datetime: lambda v: v.isoformat().replace('+00:00', 'Z'),
            datetime.time: _dump_time,
            datetime.date: lambda v: v.isoformat()
        }

    def get_empty_table(self):
        return self._dict()

    def dump_list(self, v):
        import numpy as np
        dim = np.array(v).ndim
        seperator = ",\n" if dim == 2 else ","
        retval = "["
        for u in v:
            retval += " " + unicode(self.dump_value(u)) + seperator
        retval = retval.strip("\n")
        retval = retval.strip(",")
        retval += " ]"
        return retval

    def dump_inline_table(self, section):
        """Preserve inline table in its compact syntax instead of expanding
        into subsection.
        https://github.com/toml-lang/toml#user-content-inline-table
        """
        retval = ""
        if isinstance(section, dict):
            val_list = []
            for k, v in section.items():
                val = self.dump_inline_table(v)
                val_list.append(k + " = " + val)
            retval += "{ " + ", ".join(val_list) + " }\n"
            return retval
        else:
            return unicode(self.dump_value(section))

    def dump_value(self, v):
        # Lookup function corresponding to v's type
        dump_fn = self.dump_funcs.get(type(v))
        if dump_fn is None and hasattr(v, '__iter__'):
            dump_fn = self.dump_funcs[list]
        # Evaluate function (if it exists) else return v
        return dump_fn(v) if dump_fn is not None else self.dump_funcs[str](v)

    def dump_sections(self, o, sup):
        retstr = ""
        if sup != "" and sup[-1] != ".":
            sup += '.'
        retdict = self._dict()
        arraystr = ""
        for section in o:
            section = unicode(section)
            qsection = section
            if not re.match(r'^[A-Za-z0-9_-]+$', section):
                qsection = _dump_str(section)
            if not isinstance(o[section], dict):
                arrayoftables = False
                if isinstance(o[section], list):
                    for a in o[section]:
                        if isinstance(a, dict):
                            arrayoftables = True
                if arrayoftables:
                    arraystr += "\n"
                    for a in o[section]:
                        arraytabstr = ""
                        arraystr += "[[" + sup + qsection + "]]\n"
                        s, d = self.dump_sections(a, sup + qsection)
                        if s:
                            if s[0] == "[":
                                arraytabstr += "\n" + s
                            else:
                                arraystr += s
                        while d:
                            newd = self._dict()
                            for dsec in d:
                                s1, d1 = self.dump_sections(d[dsec], sup +
                                                            qsection + "." +
                                                            dsec)
                                if s1:
                                    arraytabstr += ("[" + sup + qsection +
                                                    "." + dsec + "]\n")
                                    arraytabstr += s1
                                for s1 in d1:
                                    newd[dsec + "." + s1] = d1[s1]
                            d = newd
                        arraystr += arraytabstr + "\n"
                else:
                    if o[section] is not None:
                        retstr += (qsection + " = " +
                                   unicode(self.dump_value(o[section])) + '\n')
            elif self.preserve and isinstance(o[section], InlineTableDict):
                retstr += (qsection + " = " +
                           self.dump_inline_table(o[section]))
            else:
                retdict[qsection] = o[section]
        retstr += arraystr
        return (retstr, retdict)
